﻿#include "logs.h"

#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>

#include <sys/stat.h>

#ifndef _MSC_VER
#define localtime_s(tm, tt) localtime_r((tt), (tm))
#endif

logs logs::glog;

logs::logs()
	: level(info)
	, detail(false)
	, limit(100 * 1024 * 1024)
	, m_stream(&std::cout)
	, m_size(0)
{
}

logs::~logs()
{
}

bool logs::tie(const char *file)
{
	std::lock_guard<std::mutex> guard(m_mutex);
	m_fstream.close();
	m_stream = &std::cout;
	if (!file || strcmp("-", file) == 0) {
		m_name.clear();
		m_size = 0;
		return true;
	}
	m_fstream.open(file, std::ios::out | std::ios::app | std::ios::ate);
	if (!m_fstream.is_open()) return false;
	m_stream = &m_fstream;
	m_name = file;
	m_size = m_fstream.tellp();
	return true;
}

bool logs::retie()
{
	if (m_name.empty()) return true;
	char tmbuf[24];
	time_t now = time(NULL);
	tm loc_now;
	localtime_s(&loc_now, &now);
	strftime(tmbuf, sizeof(tmbuf), "-%y%m%d", &loc_now);
	std::string name = m_name + tmbuf;
	m_fstream.close();
	if (rename(m_name.c_str(), name.c_str()) != 0) return false;
	m_fstream.open(m_name, std::ios::out | std::ios::trunc);
	if (!m_fstream.is_open()) return false;
	m_size = 0;
	return true;
}

int logs::logf(type lv, const char *fn, int ln, const char *fmt, ...) noexcept
{
	const char* lvstr, *prstr;
	if (lv > level) return 0;
	switch (lv) {
	case error:
		lvstr = "ERROR";
		prstr = "\033[31m%s\033[0m";
		break;
	case warn:
		lvstr = " WARN";
		prstr = "\033[33m%s\033[0m";
		break;
	case info:
		lvstr = " INFO";
		prstr = "%s\n";
		break;
	case debug:
		lvstr = "DEBUG";
		prstr = "\033[32m%s\033[0m";
		break;
	default:
		return -1;
	}

	auto now = std::chrono::duration_cast<std::chrono::microseconds>(
		std::chrono::system_clock::now().time_since_epoch()).count();
	time_t tt = now / std::micro::den;
	struct tm tm;
	localtime_s(&tm, &tt);

	char buff[256], *out = buff;
	int prefix = snprintf(out, sizeof(buff),
		"%d-%02d-%02d %02d:%02d:%02d.%06d [%s][%.32s:%d] ",
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
		tm.tm_hour, tm.tm_min, tm.tm_sec,
		(int)(now % std::micro::den), lvstr, fn, ln);
	if (prefix < 0) return -1;

	va_list args;
	va_start(args, fmt);
	int suffix = vsnprintf(out + prefix, sizeof(buff) - prefix, fmt, args);
	va_end(args);
	if (suffix < 0) return -1;

	int count = prefix + suffix + 1;
	if (count > (int)sizeof(buff)) {
		if (detail) {
			out = new char[count];
			memcpy(out, buff, prefix);
			va_start(args, fmt);
			suffix = vsnprintf(out + prefix, count - prefix, fmt, args);
			va_end(args);
		} else {
			count = (int)sizeof(buff);
		}
	}

	std::lock_guard<std::mutex> guard(m_mutex);
	if (m_fstream.is_open() && m_fstream.good()) {
		out[count - 1] = '\n';
		m_stream->write(out, count);
		m_size += count;
		m_stream->flush();
		if (m_size > limit) retie();
	} else {
		printf(prstr, out);
	}
	if (out != buff) delete[] out;
	return count;
}

// |0000  00 11 22 33 44 55 66 77  88 99 aa bb cc dd ee ff |0123456789abcdef|
void logs::hexdump(std::ostream &stream, const char *str, size_t len)
{
	const unsigned char *pos = (unsigned char*)str;
	const unsigned char *end = pos + len;
	const char *table = "0123456789abcdef";
	char line[75] = "|0000  ", *hex = line + 7, *chr = hex + 50;
	uint16_t row = 0;
	while (pos < end) {
		int i = 0, j = 0;
		for (int c = 0; c < 16; ++c) {
			if (pos < end) {
				hex[i++] = table[*pos >> 4];
				hex[i++] = table[*pos & 15];
				chr[j++] = isprint(*pos) ? *pos : '.';
				pos++;
			} else {
				hex[i++] = ' ';
				hex[i++] = ' ';
				chr[j++] = ' ';
			}
			hex[i++] = ' ';
			if (c == 7) {
				hex[i++] = ' ';
			}
		}
		hex[i++] = '|';
		chr[j++] = '|';
		chr[j++] = '\n';
		for (int c = 0; c < 3; ++c) {
			line[3 - c] = table[(row >> (c * 4)) & 0xF];
		}
		stream.write(line, 75);
		row++;
	}
}

